package fr.vahren.dotastrat.gui;

import java.util.ArrayList;
import java.util.List;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

import com.trolltech.qt.core.Qt.DockWidgetArea;
import com.trolltech.qt.gui.QMainWindow;
import com.trolltech.qt.gui.QStatusBar;

import fr.vahren.dotastrat.game.D2SGame;
import fr.vahren.dotastrat.game.D2SGameState;
import fr.vahren.dotastrat.game.D2SMessage;
import fr.vahren.dotastrat.game.D2SMessageType;
import fr.vahren.dotastrat.model.D2SAbility;
import fr.vahren.dotastrat.model.D2SAncient;
import fr.vahren.dotastrat.model.D2SBarracks;
import fr.vahren.dotastrat.model.D2SCreepType;
import fr.vahren.dotastrat.model.D2SCreeps;
import fr.vahren.dotastrat.model.D2SHeroStats;
import fr.vahren.dotastrat.model.D2SHeroType;
import fr.vahren.dotastrat.model.D2SHeroUnit;
import fr.vahren.dotastrat.model.D2SItem;
import fr.vahren.dotastrat.model.D2SMap;
import fr.vahren.dotastrat.model.D2SPlayer;
import fr.vahren.dotastrat.model.D2SSide;
import fr.vahren.dotastrat.model.D2STower;
import fr.vahren.dotastrat.model.D2SUnitClass;
import fr.vahren.dotastrat.model.D2SWards;
import fr.vahren.dotastrat.model.D2SZone;
import fr.vahren.dotastrat.net.D2SClientNetworkManager;

public class D2SWindow extends QMainWindow {

	private D2SGameStatusWidget gameStatusWidget;
	private D2SHeroWidget heroWidget;
	private D2SSelectionInfoWidget selectionInfoWidget;
	private D2STeamStatusWidget teamStatusWidget;
	private D2SMapWidget mapWidget;
	private QStatusBar statusBar;

	private D2SGame model;
	private List<D2SMessage> messages;

	public D2SWindow() {
		// a window has : the map at the center
		mapWidget = new D2SMapWidget();
		setCentralWidget(mapWidget);

		// 4 dock widgets
		// game status
		gameStatusWidget = new D2SGameStatusWidget();
		addDockWidget(DockWidgetArea.TopDockWidgetArea, gameStatusWidget);

		// team status
		teamStatusWidget = new D2STeamStatusWidget();
		addDockWidget(DockWidgetArea.LeftDockWidgetArea, teamStatusWidget);

		// selection info
		selectionInfoWidget = new D2SSelectionInfoWidget();
		addDockWidget(DockWidgetArea.RightDockWidgetArea, selectionInfoWidget);

		// hero status and action
		heroWidget = new D2SHeroWidget();
		addDockWidget(DockWidgetArea.BottomDockWidgetArea, heroWidget);

		// a status bar
		statusBar = new QStatusBar();
		setStatusBar(statusBar);

		// Connections between widgets
		heroWidget.status.connect(statusBar, "showMessage(String)");

		messages = new ArrayList<>();
	}

	/**
	 * @return the gameStatusWidget
	 */
	public D2SGameStatusWidget getGameStatusWidget() {
		return gameStatusWidget;
	}

	/**
	 * @return the heroWidget
	 */
	public D2SHeroWidget getHeroWidget() {
		return heroWidget;
	}

	/**
	 * @return the selectionInfoWidget
	 */
	public D2SSelectionInfoWidget getSelectionInfoWidget() {
		return selectionInfoWidget;
	}

	/**
	 * @return the teamStatusWidget
	 */
	public D2STeamStatusWidget getTeamStatusWidget() {
		return teamStatusWidget;
	}

	/**
	 * @return the mapWidget
	 */
	public D2SMapWidget getMapWidget() {
		return mapWidget;
	}

	/**
	 * @return the statusBar
	 */
	public QStatusBar getStatusBar() {
		return statusBar;
	}

	public void processResponse(String string) {
		System.out.println("processing response : " + string);
		JSONParser parser = new JSONParser();
		JSONObject result;
		try {
			result = (JSONObject) parser.parse(string);
		} catch (ParseException e) {
			e.printStackTrace();
			return;
		}
		updateGameModel(result);
		updateGUI();
	}

	private void updateGUI() {

		updateMap();
		updateGameStatuts();
		updateTeamStatus();

	}

	private void updateTeamStatus() {
		// only for the player
		int id = Integer.parseInt(heroWidget.getActionList().getPlayerId());
		teamStatusWidget.updatePlayerUnits(model.getPlayer(id));
	}

	private void updateGameStatuts() {
		gameStatusWidget.setPhase(model.getPhase());
	}

	private void updateMap() {
		for (D2SZone zone : model.getMap().getZones()) {
			updateZone(zone);
		}
	}

	private void updateZone(D2SZone zone) {
		D2SZoneWidget z = mapWidget.getZone(zone.getId());
		int dHeros = 0;
		int rHeros = 0;
		D2SCreepType creep2 = null;
		D2SCreepType creep1 = null;
		boolean choose = false;
		boolean dActive = false;
		boolean rActive = false;
		int dCreeps = 0;
		int rCreeps = 0;
		boolean dObs = false;
		boolean rObs = false;
		boolean dWards = false;
		boolean rWards = false;
		for (D2SHeroUnit unit : zone.getUnits()) {
			if (model.getPlayerFromUnit(unit.getId()).getSide() == D2SSide.DIRE) {
				dHeros++;
			} else {
				rHeros++;
			}
		}
		for (D2SCreeps creep : zone.getCreeps()) {
			switch (creep.getType()) {
			case DIRE:
				dCreeps++;
				break;
			case RADIANT:
				rCreeps++;
				break;
			case ANCIENT:
				creep1 = D2SCreepType.ANCIENT;
				break;
			case BIG:
			case MEDIUM:
			case SMALL:
				if (zone.getCreepType1() == creep.getType()) {
					creep1 = creep.getType();
				} else {
					creep2 = creep.getType();
				}
				break;
			case ROSHAN:
				creep1 = D2SCreepType.ROSHAN;
				break;
			}
		}
		for (D2SWards ward : zone.getWards()) {
			if (ward.isObserver()) {
				if (ward.getSide() == D2SSide.DIRE) {
					dObs = true;
				} else {
					rObs = true;
				}
			} else {
				if (ward.getSide() == D2SSide.DIRE) {
					dWards = true;
				} else {
					rWards = true;
				}
			}
		}

		// update it
		z.update(rHeros, dHeros, rWards, dWards, rObs, dObs, rCreeps, dCreeps,
				rActive, dActive, choose, creep1, creep2);
	}

	public void updateGameModel(JSONObject resp) {
		if (model == null) {
			initGameModel(resp);
		} else {
			// update
			// game status
			updateModelGameStatus(resp);

			// players
			updateModelPlayers(resp);

			// zones
			updateModelMap(resp);
		}
	}

	private void updateModelMap(JSONObject resp) {
		JSONObject mapJson = (JSONObject) resp.get("map");
		JSONArray zonesJson = (JSONArray) mapJson.get("zones");
		for (Object z : zonesJson) {
			updateModelZone((JSONObject) z);
		}
	}

	private void updateModelZone(JSONObject z) {
		int id = ((Long) z.get("id")).intValue();
		D2SZone zone = model.getMap().getZone(id);
		fillZoneWithJSON(z, zone);
	}

	private void updateModelPlayers(JSONObject resp) {
		// players can't change, only their units and messages
		for (Object p : (JSONArray) resp.get("players")) {
			int id = ((Long) ((JSONObject) p).get("id")).intValue();
			getNewMessages((JSONObject) p);
			for (Object u : (JSONArray) ((JSONObject) p).get("units")) {
				updateModelUnit(id, (JSONObject) u);
			}
		}
	}

	private void updateModelUnit(int playerId, JSONObject u) {
		D2SHeroUnit unit = (D2SHeroUnit) model.getUnit(((Long) ((JSONObject) u)
				.get("id")).intValue());
		if (unit == null) {
			unit = buildUnit(u);
			model.getPlayer(playerId).addControlledUnit(unit);
		} else {
			unit.setStats(buildStats((JSONObject) u.get("stats")));
		}

	}

	private void updateModelGameStatus(JSONObject resp) {
		model.setPhase(D2SGameState.valueOf(((JSONObject) resp
				.get("gameStatus")).get("phase").toString()));
	}

	private void initGameModel(JSONObject resp) {
		model = new D2SGame();
		model.setId(((Long) ((JSONObject) resp.get("gameStatus")).get("id"))
				.intValue());
		model.setPhase(D2SGameState.valueOf((String) ((JSONObject) resp
				.get("gameStatus")).get("phase")));
		// init players
		JSONArray playersJson = (JSONArray) resp.get("players");
		List<D2SPlayer> players = new ArrayList<>();
		for (Object p : playersJson) {
			D2SPlayer player = buildPlayer((JSONObject) p);
			players.add(player);
		}
		model.setPlayers(players);

		// init zones
		JSONObject mapJson = (JSONObject) resp.get("map");
		JSONArray zonesJson = (JSONArray) mapJson.get("zones");
		List<D2SZone> zones = new ArrayList<>();
		for (Object z : zonesJson) {
			D2SZone zone = buildZone((JSONObject) z);
			zones.add(zone);
		}
		D2SMap map = new D2SMap(model);
		model.setMap(map);
		D2SMap.setZones(zones);
		// ancients
		map.setDireAncient(buildAncient((JSONObject) mapJson.get("direAncient")));
		map.setRadiantAncient(buildAncient((JSONObject) mapJson
				.get("radiantAncient")));

	}

	private D2SAncient buildAncient(JSONObject object) {
		// TODO Auto-generated method stub
		return null;
	}

	private D2SZone buildZone(JSONObject z) {

		D2SBarracks meleeBarracks = null;// TODO
		D2SBarracks rangedBarracks = null;
		List<D2STower> towers = new ArrayList<>();
		int id = ((Long) z.get("id")).intValue();

		D2SZone zone = new D2SZone(id, null, null, towers, rangedBarracks,
				meleeBarracks, null);

		fillZoneWithJSON(z, zone);

		return zone;
	}

	private void fillZoneWithJSON(JSONObject z, D2SZone zone) {
		// fill units
		List<D2SHeroUnit> units = new ArrayList<>();
		for (Object u : (JSONArray) z.get("units")) {
			int unitId = ((Long) u).intValue();
			units.add((D2SHeroUnit) model.getUnit(unitId));
		}
		zone.setUnits(units);
		// fill creeps
		List<D2SCreeps> creeps = new ArrayList<>();
		for (Object c : (JSONArray) z.get("creeps")) {
			creeps.add(buildCreep((JSONObject) c));
		}
		zone.setCreeps(creeps);
	}

	private D2SCreeps buildCreep(JSONObject c) {
		return new D2SCreeps(D2SCreepType.valueOf(c.get("type").toString()), 0,
				false);
	}

	private D2SPlayer buildPlayer(JSONObject p) {
		D2SSide side = D2SSide.valueOf((String) p.get("side"));
		D2SPlayer player = new D2SPlayer(side);
		// player attributes
		player.setId(((Long) p.get("id")).intValue());

		// init units
		List<D2SHeroUnit> controlledUnits = new ArrayList<>();
		for (Object u : (JSONArray) p.get("units")) {
			D2SHeroUnit unit = buildUnit((JSONObject) u);
			controlledUnits.add(unit);
		}
		player.setControlledUnits(controlledUnits);

		getNewMessages(p);

		return player;
	}

	private void getNewMessages(JSONObject p) {
		// append new messages
		for (Object m : (JSONArray) p.get("messages")) {
			D2SMessage message = buildMessage((JSONObject) m);
			addNewMessage(message);
			// update console
			selectionInfoWidget.updateConsole(message);
		}
	}

	private void addNewMessage(D2SMessage message) {
		messages.add(message);
	}

	private D2SMessage buildMessage(JSONObject m) {
		return new D2SMessage((String) m.get("text"),
				D2SMessageType.valueOf((String) m.get("type")));
	}

	private D2SHeroUnit buildUnit(JSONObject u) {
		int id = ((Long) u.get("id")).intValue();
		D2SUnitClass unitClass = D2SUnitClass.valueOf((String) u.get("class"));
		String name = (String) u.get("name");
		D2SHeroUnit unit = new D2SHeroUnit(name, unitClass, id, model);
		unit.setIllusion((boolean) u.get("isIllusion"));
		// items
		List<D2SItem> items = new ArrayList<>();
		for (Object i : (JSONArray) u.get("items")) {
			items.add(buildItem((JSONObject) i));
		}
		unit.setItems(items);
		// abilities
		List<D2SAbility> abilities = new ArrayList<>();
		for (Object i : (JSONArray) u.get("items")) {
			abilities.add(buildAbility((JSONObject) i));
		}
		unit.setAbilities(abilities);
		unit.setStats(buildStats((JSONObject) u.get("stats")));

		unit.setReliableGold(((Long) u.get("reliableGold")).intValue());
		unit.setUnreliableGold(((Long) u.get("unreliableGold")).intValue());
		unit.setNonUsedSkillPoints(((Long) u.get("skillPoints")).intValue());

		unit.setHasChosenAction((boolean) u.get("hasChosenAction"));

		return unit;
	}

	private D2SHeroStats buildStats(JSONObject s) {
		D2SHeroStats stats = new D2SHeroStats();

		stats.setAgi(((Long) s.get("agi")).intValue());
		stats.setStr(((Long) s.get("str")).intValue());
		stats.setIntel(((Long) s.get("intel")).intValue());

		stats.setHealth(((Long) s.get("health")).intValue());
		stats.setMana(((Long) s.get("mana")).intValue());
		stats.setLevel(((Long) s.get("level")).intValue());
		stats.setExp(((Long) s.get("exp")).intValue());
		stats.setType(D2SHeroType.valueOf((String) s.get("type")));
		stats.setBaseDamage(((Long) s.get("baseDamage")).intValue());

		return stats;
	}

	private D2SAbility buildAbility(JSONObject i) {
		// TODO Auto-generated method stub
		return null;
	}

	private D2SItem buildItem(JSONObject i) {
		// TODO Auto-generated method stub
		return null;
	}

	public void start(boolean b) {
		D2SClientNetworkManager.getInstance().sendStart("1",
				heroWidget.getActionList().getPlayerId());
	}

	public void end(boolean b) {
		D2SClientNetworkManager.getInstance().sendEnd("1",
				heroWidget.getActionList().getPlayerId());
	}

	public void atk(boolean b) {
		D2SClientNetworkManager.getInstance().sendAttack("1",
				heroWidget.getActionList().getPlayerId(),
				heroWidget.getActionList().getHeroId(),
				heroWidget.getActionList().getTargetId(), true);
	}

	public void move(boolean b) {
		D2SClientNetworkManager.getInstance().sendMove("1",
				heroWidget.getActionList().getPlayerId(),
				heroWidget.getActionList().getHeroId(),
				heroWidget.getActionList().getZoneId(), true);
	}

	public void info(boolean b) {
		D2SClientNetworkManager.getInstance().sendInfo("1",
				heroWidget.getActionList().getPlayerId());

	}

	public void chooseZone(int zoneId) {
		heroWidget.getActionList().setZoneId(String.valueOf(zoneId));

		// show information about this zone
		selectionInfoWidget.displayZoneInfo(zoneId, model);
	}

}
